using System;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NQueries.TestSupport;
using Rhino.Mocks;

namespace NQueries.Data.Test
{
    [TestClass]
    public class UnitOfWorkTest
    {
        public TestContext TestContext { get; set; }

        [TestInitialize]
        public void TestInitialize() { MockTools.Reset(); }

        [TestMethod]
        public void BeginStartsTransactionTest()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            using (MockTools.Record())
                Expect.Call(dataContext.Dispose);

            using (MockTools.Playback())
            {
                Assert.IsNull(Transaction.Current);

                using (IUnitOfWork target = UnitOfWork.Begin(TransactionScopeOption.Required))
                {
                    Assert.AreEqual(target.DataContext, dataContext);
                    Assert.IsNotNull(Transaction.Current);
                }
            }
        }

        [TestMethod]
        public void BeginStartsNoTransactionTest()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            using (MockTools.Record())
                Expect.Call(dataContext.Dispose);

            using (MockTools.Playback())
            {
                Assert.IsNull(Transaction.Current);

                using (UnitOfWork.Begin(TransactionScopeOption.Suppress))
                    Assert.IsNull(Transaction.Current);
            }
        }

        [TestMethod]
        public void NestedBeginJoinsTransactionTest()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Repeat.Times(2).Return(0);
                Expect.Call(dataContext.Dispose).Repeat.Times(2);
            }

            using (MockTools.Playback())
            {
                Assert.IsNull(Transaction.Current);

                using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required))
                {
                    Assert.IsNotNull(Transaction.Current);
                    string outerTransactionId = Transaction.Current.TransactionInformation.LocalIdentifier;
                    using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin(TransactionScopeOption.Required))
                    {
                        Assert.IsNotNull(Transaction.Current);
                        string innerTransactionId = Transaction.Current.TransactionInformation.LocalIdentifier;
                        Assert.AreEqual(innerTransactionId, outerTransactionId);
                        unitOfWork2.Commit();
                    }
                    unitOfWork.Commit();
                }
            }
        }

        [TestMethod]
        public void NestedBeginStartsNewTransactionTest()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Repeat.Times(2).Return(0);
                Expect.Call(dataContext.Dispose).Repeat.Times(2);
            }

            using (MockTools.Playback())
            {
                Assert.IsNull(Transaction.Current);

                using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required))
                {
                    Assert.IsNotNull(Transaction.Current);
                    string outerTransactionId = Transaction.Current.TransactionInformation.LocalIdentifier;
                    using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin(TransactionScopeOption.RequiresNew))
                    {
                        Assert.IsNotNull(Transaction.Current);
                        string innerTransactionId = Transaction.Current.TransactionInformation.LocalIdentifier;
                        Assert.AreNotEqual(innerTransactionId, outerTransactionId);
                        unitOfWork2.Commit();
                    }
                    unitOfWork.Commit();
                }
            }
        }

        [TestMethod]
        public void NestedBeginWithoutTransactionTest()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Repeat.Times(2).Return(0);
                Expect.Call(dataContext.Dispose).Repeat.Times(2);
            }

            using (MockTools.Playback())
            {
                Assert.IsNull(Transaction.Current);

                using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required))
                {
                    Assert.IsNotNull(Transaction.Current);
                    using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin(TransactionScopeOption.Suppress))
                    {
                        Assert.IsNull(Transaction.Current);
                        unitOfWork2.Commit();
                    }
                    Assert.IsNotNull(Transaction.Current);
                    unitOfWork.Commit();
                }
            }
        }

        [TestMethod]
        public void TransactionAndContextDoNotSpanMultipleThreads()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Repeat.Times(2).Return(0);
                Expect.Call(dataContext.Dispose).Repeat.Times(2);
            }

            using (MockTools.Playback())
            {
                Assert.IsNull(Transaction.Current);

                using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TransactionScopeOption.Required))
                {
                    Assert.IsNotNull(Transaction.Current);
                    string outerTransactionId = Transaction.Current.TransactionInformation.LocalIdentifier;

                    Task task = new Task(() =>
                    {
                        using (IUnitOfWork unitOfWork2 = UnitOfWork.Begin(TransactionScopeOption.Required))
                        {
                            Assert.IsNotNull(Transaction.Current);
                            string innerTransactionId = Transaction.Current.TransactionInformation.LocalIdentifier;
                            Assert.AreNotEqual(innerTransactionId, outerTransactionId);
                            unitOfWork2.Commit();
                        }
                    });
                    task.Start();
                    Task[] tasks = new[] { task };
                    Task.WaitAll(tasks);
                    unitOfWork.Commit();
                }
            }
        }

        [TestMethod]
        public void UnitOfWorkConstructorTest1()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            const TransactionScopeOption TRANSACTION_SCOPE_OPTION = TransactionScopeOption.Suppress;
            const string NAME = "TEST";

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Return(0);
                Expect.Call(dataContext.Dispose);
            }

            using (MockTools.Playback())
                using (IUnitOfWork target = UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION, NAME))
                {
                    Assert.AreEqual(NAME, target.Name);
                    Assert.IsNull(Transaction.Current);
                    target.Commit();
                }
        }

        [TestMethod]
        public void UnitOfWorkConstructorTest2()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            const TransactionScopeOption TRANSACTION_SCOPE_OPTION = TransactionScopeOption.Required;
            const string NAME = "TEST";

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Return(0);
                Expect.Call(dataContext.Dispose);
            }

            using (MockTools.Playback())
                using (IUnitOfWork target = UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION, NAME))
                {
                    Assert.AreEqual(NAME, target.Name);
                    Assert.IsNotNull(Transaction.Current);
                    target.Commit();
                }
        }

        [TestMethod]
        public void UnitOfWorkConstructorTest3()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            const TransactionScopeOption TRANSACTION_SCOPE_OPTION = TransactionScopeOption.Required;
            const string NAME = "TEST";

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Return(0).Repeat.Times(2);
                Expect.Call(dataContext.Dispose).Repeat.Times(2);
            }

            using (MockTools.Playback())
                using (IUnitOfWork unitOfOWork = UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION, NAME))
                {
                    using (IUnitOfWork unitOfOWork2 = UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION, NAME))
                    {
                        Assert.AreEqual(NAME, unitOfOWork2.Name);
                        Assert.IsNotNull(Transaction.Current);
                        unitOfOWork2.Commit();
                    }
                    unitOfOWork.Commit();
                }
        }

        [TestMethod]
        public void UnitOfWorkConstructorTest4()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            const string NAME = "TEST";

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Return(0);
                Expect.Call(dataContext.Dispose);
            }

            using (MockTools.Playback())
                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
                {
                    DependentTransaction dependentTransaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
                    using (IUnitOfWork target = UnitOfWork.Begin(dependentTransaction, NAME))
                    {
                        Assert.AreEqual(NAME, target.Name);
                        Assert.AreEqual(dependentTransaction, new PrivateObject(target).GetProperty("DependentTransaction"));
                        Assert.IsNotNull(Transaction.Current);
                        target.Commit();
                    }
                    scope.Complete();
                }
        }

        [TestMethod]
        public void BeginTest()
        {
            MockTools.StubInject<IDataContext>();

            const TransactionScopeOption TRANSACTION_SCOPE_OPTION = TransactionScopeOption.Required;
            const string NAME = "TEST";

            using (IUnitOfWork target = UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION, NAME))
            {
                Assert.AreEqual(NAME, target.Name);
                Assert.IsNotNull(Transaction.Current);
            }
        }

        [TestMethod]
        public void BeginTest1()
        {
            MockTools.StubInject<IDataContext>();

            const TransactionScopeOption TRANSACTION_SCOPE_OPTION = TransactionScopeOption.Required;

            using (UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION))
                Assert.IsNotNull(Transaction.Current);
        }

        [TestMethod]
        public void BeginTest2()
        {
            MockTools.StubInject<IDataContext>();

            using (TransactionScope scope = new TransactionScope())
            {
                DependentTransaction dependentTransaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
                using (IUnitOfWork target = UnitOfWork.Begin(dependentTransaction))
                {
                    Assert.IsNotNull(Transaction.Current);
                    Assert.AreEqual(dependentTransaction, new PrivateObject(target).GetProperty("DependentTransaction"));
                    target.Commit();
                }

                scope.Complete();
            }
        }

        [TestMethod]
        public void BeginTest3()
        {
            const string NAME = "Test";
            MockTools.StubInject<IDataContext>();

            using (TransactionScope scope = new TransactionScope())
            {
                DependentTransaction dependentTransaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
                using (IUnitOfWork target = UnitOfWork.Begin(dependentTransaction, NAME))
                {
                    Assert.IsNotNull(Transaction.Current);
                    Assert.AreEqual(NAME, target.Name);
                    Assert.AreEqual(dependentTransaction, new PrivateObject(target).GetProperty("DependentTransaction"));
                    target.Commit();
                }

                scope.Complete();
            }
        }

        [TestMethod]
        public void ToStringTest()
        {
            MockTools.StubInject<IDataContext>();

            const TransactionScopeOption TRANSACTION_SCOPE_OPTION = TransactionScopeOption.Required;
            string name = Guid.NewGuid().ToString();

            using (IUnitOfWork unitOfWork = UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION, name))
                Assert.IsTrue(unitOfWork.ToString().Contains(name));
        }

        [TestMethod]
        public void DataContextTest()
        {
            IDataContext dataContext = MockTools.StubInject<IDataContext>();

            const TransactionScopeOption TRANSACTION_SCOPE_OPTION = TransactionScopeOption.Required;

            using (MockTools.Record()) {}

            using (MockTools.Playback())
                using (UnitOfWork.Begin(TRANSACTION_SCOPE_OPTION))
                    Assert.AreEqual(dataContext, UnitOfWork.CurrentDataContext);
        }

        [TestMethod]
        public void GetDependentTransactionTest()
        {
            MockTools.StubInject<IDataContext>();

            using (MockTools.Record()) {}

            using (MockTools.Playback())
                using (IUnitOfWork target = UnitOfWork.Begin(TransactionScopeOption.Required))
                    using (DependentTransaction actual = target.GetDependentTransaction(DependentCloneOption.BlockCommitUntilComplete))
                    {
                        Assert.IsNotNull(actual);
                        actual.Complete();
                    }
        }

        [TestMethod]
        public void GetDependentTransactionFailedTest()
        {
            MockTools.StubInject<IDataContext>();

            using (MockTools.Record()) {}

            using (MockTools.Playback())
                using (IUnitOfWork target = UnitOfWork.Begin(TransactionScopeOption.Suppress))
                    TestTools.AssertThrows<NQueriesException>(
                        () =>
                        {
                            using (DependentTransaction actual = target.GetDependentTransaction(DependentCloneOption.BlockCommitUntilComplete))
                            {
                                Assert.IsNotNull(actual);
                                actual.Complete();
                            }
                        });
        }

        [TestMethod]
        public void CommitReturnValueTest()
        {
            IDataContext dataContext = MockTools.StrictInject<IDataContext>();

            using (MockTools.Record())
            {
                Expect.Call(dataContext.SaveChanges()).Return(3);
                Expect.Call(dataContext.Dispose);
            }

            using (MockTools.Playback())
                using (IUnitOfWork target = UnitOfWork.Begin())
                    Assert.AreEqual(3, target.Commit());
        }

        #region Nested type: Test

        public class Test {}

        #endregion
    }
}